package org.linuxsogood.boot.future;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

import static java.util.stream.Collectors.toList;

/**
 * @author honway.liu
 * @date 2017/8/3 下午9:42
 * @email gm100861@gmail.com
 * @blog http://linuxsogood.org
 */
public class Shop {

    private static Random random = new Random();

    private String name;


    static List<Shop> shopes = Arrays.asList(new Shop("BestPrice"), new Shop("LetsSaveBig"), new Shop("MyFavoriteShop"), new Shop("BuyItAll"), new Shop("BuyNow"));

    private static final Executor executor = Executors.newFixedThreadPool(Math.min(shopes.size(), 100), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setDaemon(true);
            return thread;
        }
    });

    public Shop(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice(String product) {
        return calculatePrice(product);
    }

    public double calculatePrice(String product) {
        delay();
        return random.nextDouble() * product.charAt(0) * product.charAt(1);
    }

    public static void delay() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public Future<Double> getPriceAsync(String product) {
        CompletableFuture<Double> completableFuture = new CompletableFuture<>();
        try {
            new Thread(() -> {
                double price = calculatePrice(product);
                System.out.println(1 / 0);
                completableFuture.complete(price);
            }).start();
        } catch (Exception e) {
            completableFuture.completeExceptionally(e);
        }
        return completableFuture;
    }

    public static List<String> findPrices(String product) {
        return shopes.stream().map(shop -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product))).collect(toList());
    }

    public static List<String> findPricesParallel(String product) {
        return shopes.parallelStream().map(shop -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product))).collect(toList());
    }



    public static List<String> findPricesFuture(String product) {
        List<CompletableFuture<String>> completableFutures = shopes.stream().map(shop -> CompletableFuture.supplyAsync(() -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product)), executor)).collect(toList());
        return completableFutures.stream().map(CompletableFuture::join).collect(toList());
    }




    public static void main(String[] args) {
        /*Shop bestShop = new Shop("BestShop");
        long start = System.nanoTime();
        Future<Double> futurePrice = bestShop.getPriceAsync("my favorite product");
        long invocationTime = (System.nanoTime() - start) / 1_000_000;
        System.out.println("Invocation returned after " + invocationTime + "msecs");
        System.out.println("do something else.");
        try {
            Double price = futurePrice.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }*/
        long start = System.nanoTime();
        System.out.println(findPrices("myPhone27s"));
        long invocationTime = (System.nanoTime() - start) / 1_000_000;
        System.out.println("Invocation returned after " + invocationTime + "msecs");

        start = System.nanoTime();
        System.out.println(findPricesParallel("myPhone27s"));
        invocationTime = (System.nanoTime() - start) / 1_000_000;
        System.out.println("parallel Invocation returned after " + invocationTime + "msecs");

        start = System.nanoTime();
        System.out.println(findPricesFuture("myPhone27s"));
        invocationTime = (System.nanoTime() - start) / 1_000_000;
        System.out.println("future Invocation returned after " + invocationTime + "msecs");
    }
}
